<?php

/* 
 * Copyright (c) 2009 Nguyen Duc Thuan <me@ndthuan.com>
 * All rights reserved.
 */

/**
 * Text highlighter without affecting HTML tags. This supports highlighting
 * string in the double quotes of keyword for compatibility with some
 * search methods such as MySQL Full-text search, Google, Yahoo...
 *
 * Sample usage:
 * - Method 1:
 *      $highlightedString = Fete_Util_Text_Highlighter::createInstance(
 *          '<b>', '</b>'
 *      )->highlight('PHP rules', 'rules');
 * - Method 2:
 *      $highlighter = new Fete_Util_Text_Highlighter();
 *      $highlightedString = $highlighter->setBeforeMatch('<b>')
 *                  ->setAfterMatch('</b>')
 *                  ->highlight('PHP rules the world', '"PHP rules"');
 *
 * @author Nguyen Duc Thuan <me@ndthuan.com>
 */
class Fete_Util_Text_Highlighter
{
    /**
     * @var string
     */
    protected $_beforeMatch = '<span style="background-color:yellow">';

    /**
     * @var string
     */
    protected $_afterMatch = '</span>';

    /**
     *
     * @param string $beforeMatch
     * @param string $afterMatch
     */
    public function __construct($beforeMatch = null, $afterMatch = null)
    {
        if (null !== $beforeMatch) {
            $this->_beforeMatch = $beforeMatch;
        }

        if (null !== $afterMatch) {
            $this->_afterMatch = $afterMatch;
        }
    }

    /**
     *
     * @param string $beforeMatch
     * @param string $afterMatch
     * @return Fete_Util_Text_Highlighter
     */
    static public function createInstance($beforeMatch = null
        , $afterMatch = null)
    {
        return new self($beforeMatch, $afterMatch);
    }

    /**
     *
     * @param string $beforeMatch
     * @return Fete_Util_Text_Highlighter
     */
    public function &setBeforeMatch($beforeMatch)
    {
        $this->_beforeMatch = $beforeMatch;
        return $this;
    }

    /**
     *
     * @param string $afterMatch
     * @return Fete_Util_Text_Highlighter
     */
    public function &setAfterMatch($afterMatch)
    {
        $this->_afterMatch = $afterMatch;
        return $this;
    }

    /**
     *
     * @param string $text
     * @param string $keyword
     * @return string highlighted string
     */
    public function highlight($text, $keyword)
    {
        $output = '';

        $words = array();

        preg_match_all('#(?:"([^"]+)"|(?:[^\s\+\-"\(\)><~\*\'\|\\`\!@\#\$%^&_=\[\]\{\}:;,\./\?]+))#si'
            , $keyword, $matches, PREG_SET_ORDER);

        foreach ($matches as $match)
        {
            if (2 === count($match)) {
                $words[] = $match[1];
            } else {
                $words[] = $match[0];
            }
        }

        $words = implode('|', $words);

        $textParts = preg_split('#(<script[^>]*>.*?</script>|<style[^>]*>.*?</style>|<.+?>)#si'
            , $text, -1, PREG_SPLIT_DELIM_CAPTURE);

        foreach ($textParts as $byHtmlPart)
        {
            if (!empty($byHtmlPart) && $byHtmlPart{0} != '<') {
                $byHtmlPart = preg_replace('#(' . $words . ')#si'
                    , $this->_beforeMatch . '\1' . $this->_afterMatch, $byHtmlPart);
            }

            $output .= $byHtmlPart;
        }

        return $output;
    }
}
